# coding=utf-8
"""
    将 curl 转换成 python 的 requests 脚本
"""

import json
import re
from utils.api_class_conf import token_to_api_class, token_fixture
from urllib.parse import parse_qs

# @Time    :  2024-08-07 14:53:55
# @Author  :  jiangtong
# @Email   :  gingerqgyy@outlook.com
# @Project :  demo_flask_new
# @File    :  curl_to_py

headers_useful = ['terminal', 'token', 'project-id', 'user-id', 'user-type', 'access-token']


class ReqData:
    """
    url       :  请求链接
    method    :  请求方法
    headers   :  请求头  ------------>  curl 转 Python 用
    data      :  请求参数
    host      :  a / h / crm / gcp
    uri       :  /api/yft 之后的部分
    params    :  url 的 ? 之后的部分
    api_class :  BaseApi 的子类
    api_token :  token 参数, 也就是对应的 fixture 的 name
    """

    def __init__(self, url, method, headers, data):
        # url, method, headers, data :
        self.url = url
        self.method = method
        self.headers = headers
        self.data = data
        self.uri_id = ''

        # host 取 uri 中的 { a / h / gcp / crm / ky } :
        url_pre_fix = self.url.split('-test')[0]
        self.host = url_pre_fix[8 - len(url_pre_fix):]

        # 提取 uri : ( 就是 api/xxx 之后的 url )
        # 截取, { 'api/yft' / 'api/gcp' / '/api/edc' }
        if 'api/yft' in self.url:
            uri = self.url.split('/api/yft', 1)[-1] if '/api/yft' in url else url
        elif 'api/gcp' in self.url:
            uri = self.url.split('/api/gcp', 1)[-1] if '/api/gcp' in url else url
        elif 'api/edc' in self.url:  # /api/edc
            uri = self.url.split('/api/edc', 1)[-1] if '/api/edc' in url else url
        else:
            uri = self.url.split('/api/', 1)[-1] if '/api/' in url else url

        # 如果 url 中包含 ? 则截取 ? 之后的部分
        self.uri = uri.split('?', 1)[0] if '?' in uri else uri
        # 获取请求路径之后 ? 拼接的参数 :
        self.params = uri.split('?', 1)[-1] if '?' in uri else None
        # 将 params 中 ? 之后拼接的部分处理成 dict 格式 :
        parsed_params = parse_qs(self.params)
        self.params_data = {k: v[0] for k, v in parsed_params.items() if k != '_t'}

        _uri = self.uri
        _uri_id = ''

        # 如果 uri 最后是纯数值, 则去掉这个纯数值 :
        if self.uri.count('/') > 1:
            uri_li = self.uri.rsplit('/', 1)
            _uri, _uri_id = (uri_li[0], uri_li[1]) if uri_li[-1].isdigit() else (self.uri, None)
        if self.uri.count('/') > 2:
            uri_li = self.uri.rsplit('/', 2)
            if uri_li[-2].isdigit() and not uri_li[-1].isdigit():
                _uri, _uri_id = (uri_li[0], f'{uri_li[1]}/{uri_li[2]}')
        if self.uri.count('/') > 3:
            uri_li = self.uri.rsplit('/', 3)
            _condition = uri_li[-3].isdigit() and not uri_li[-2].isdigit() and not uri_li[-1].isdigit()
            if _condition:
                _uri, _uri_id = (uri_li[0], f'{uri_li[1]}/{uri_li[2]}/{uri_li[3]}')

        self.uri = _uri
        self.uri_id = _uri_id

        # 根据 headers 中的 token, 确定生成哪个 BaseApi 的子类 :
        token = headers.get('token', '')
        api_class = "BaseApi(token=token_admin, , host='h')"  # 这里是给默认值 : 不准确, 需要使用时手动修改
        api_token = 'token_admin'  # 这里是给默认值 : 不准确, 需要使用时手动修改
        if token:
            token_prefix = token.split(':', 1)[0]
            api_class = token_to_api_class.get(token_prefix, api_class)
            api_token = token_fixture.get(token_prefix, api_token)
        self.api_class = api_class
        self.api_token = api_token

    def __str__(self):
        """
            方便获取对象的属性和值, 生效于直接打印时
        """
        return str(self.__dict__)

    def __repr__(self):
        """
            方便获取对象的属性和值, 生效于返回传递时
        """
        return str(self.__dict__)


class TableData:
    """
    TODO: 用于返回数据, 不然代码传参太多 ...
    """

    def __init__(self, case_id, case_name, model, host, url, method, headers, data):
        self.case_id = case_id
        self.case_name = case_name
        self.model = model
        self.host = host
        self.url = url
        self.method = method
        self.headers = headers
        self.data = data

    def __str__(self):
        """
            方便获取对象的属性和值, 生效于直接打印时
        """
        return str(self.__dict__)

    def __repr__(self):
        """
            方便获取对象的属性和值, 生效于返回传递时
        """
        return str(self.__dict__)


# curl 提取 :
def curl_extract(_curl_command) -> ReqData:
    """
    提取 curl 命令的关键数据, url, method, headers, data
    :param _curl_command:
    :return: ReqData
    """
    # 合并行, 删除换行符 :
    _curl_command = re.sub(r'\\\s*', ' ', _curl_command)

    # 提取 url :
    url_match = re.search(r"'(https?://\S+)'", _curl_command)
    url = url_match.group(1) if url_match else ''

    # 提取请求方法, 默认是 get :
    method = 'get'
    if '-X' in _curl_command:
        # method_match = re.search(r'-X\s+(\w+)', _curl_command)
        method_match = re.search(r"-X\s+['\"]?(\w+)['\"]?", _curl_command)
        method = method_match.group(1).lower() if method_match else 'get'
    if '--data-raw' in _curl_command or '--data' in _curl_command or '-d ' in _curl_command:
        # 如果不是 get 请求, 表示正则取到了请求方法, 如果是 get 表示正则未取到, 未取到就取 post :
        method = method if method != 'get' else 'post'

    # 提取请求头 :
    headers = {}
    # 单引号
    header_matches = re.findall(r"-H\s+'([^:]+):\s*([^']+)'", _curl_command)
    if not header_matches:
        # 双引号
        header_matches = re.findall(r'-H\s+"([^:]+):\s*([^"]+)"', _curl_command)
    if not header_matches:
        # \s+ -> 空格; [^:]+ -> 非冒号; \s* -> 空格; [^ ]+ -> 非空格
        header_matches = re.findall(r'-H\s+([^:]+):\s*([^ ]+)', _curl_command)
    for header in header_matches:
        # 生成请求头 : ( 这里是仅保留 ['terminal', 'token', 'project-id', 'user-id', 'user-type', 'access-token'])
        if header[0].strip().lower() in headers_useful:
            headers[header[0].strip()] = header[1].strip()

    # 参数提取 :
    data = {}
    if '--data-raw' in _curl_command or '--data' in _curl_command or '-d' in _curl_command:
        data_match = re.search(r'--data-raw\s+\'([^\']+)\'|--data\s+\'([^\']+)\'|-d\s+\'([^\']+)\'', _curl_command)
        if data_match:

            data = data_match.group(1) or data_match.group(2) or data_match.group(3)
            try:
                data = json.loads(data)
            except json.JSONDecodeError as e:
                print(f'报错 : {e}')

    return ReqData(url=url, method=method, headers=headers, data=data)


# curl 提取, Charles 的 curl 提取 :
def curl_extract_charles(_curl_command) -> ReqData:
    """
    提取 curl 命令的关键数据, url, method, headers, data
    Charles 的 culr 特点:
        一整行, 不用处理换行符
        url 跟在 "--compressed" 参数之后
        data 跟在 "--data-binary" 参数之后
        data 中存在引号转义

    :param _curl_command:
    :return: class<ReqData>
    """
    # 合并行, 删除换行符 :
    _curl_command = re.sub(r'\\\s*', ' ', _curl_command)

    # 提取 url :
    url_match = re.search(r'"(https://[^"]*)"', _curl_command)
    url_match_1 = re.search(r"'(https://[^']*)'", _curl_command)
    url = url_match.group(1) if url_match else url_match_1.group(1) if url_match_1 else ''

    # 提取 method :
    method_match = re.search(r'-X\s+(\w+)|--request\s+(\w+)', _curl_command)
    method = method_match.group(1).lower() if method_match else ('post' if '--data-binary' in _curl_command else 'get')

    # 提取 data : ( 按照下一个参数是 - X 个 --compressed 分别提取, 哪个提取到了用哪个, 然后再转 Json )
    data = {}
    data_binary_match1 = re.search(r'--data-binary\s+"(.+)" -X', _curl_command)
    data_binary_match2 = re.search(r'--data-binary\s+"(.+)" --compressed', _curl_command)
    data_binary_match = data_binary_match1 or data_binary_match2
    if data_binary_match:
        # 获取 JSON 字符串
        data_str = data_binary_match.group(1)
        # 替换转义字符, curl 中的 json 数据是 \" , 这里替换成 " , 替换后, key 和 value 值后面会有空格, 第二个替换是去掉空格 .
        data_str = data_str.replace('\\"', '"').replace(' ', '')
        # 解析 JSON 字符串为 Python 字典
        try:
            data = json.loads(data_str)

        except json.JSONDecodeError:
            # 解析失败，返回原始字符串
            data = data_str

    # 上边的提取是针对 Charles 的, 下边的提取是比较通用的提取方式, 如果针对 Charles 的提取方式未提取到, 就再提取一次, ( 兼容微信开发者工具 )
    if not data:
        if '--data-binary' in _curl_command or '--data' in _curl_command or '-d ' in _curl_command:
            data_match = re.search(r'--data-binary\s+\'([^\']+)\'|--data\s+\'([^\']+)\'|-d\s+\'([^\']+)\'', _curl_command)
            if data_match:

                data = data_match.group(1).strip() or data_match.group(2).strip() or data_match.group(3).strip()
                try:
                    data = json.loads(data)
                except json.JSONDecodeError as e:
                    print(f'报错 : {e}')
                    raise e

    # 请求头 :
    headers = {}
    # 单引号
    header_matches = re.findall(r"-H\s+'([^:]+):\s*([^']+)'", _curl_command)
    if not header_matches:
        # 双引号
        header_matches = re.findall(r'-H\s+"([^:]+):\s*([^"]+)"', _curl_command)
    if not header_matches:
        # \s+ -> 空格; [^:]+ -> 非冒号; \s* -> 空格; [^ ]+ -> 非空格
        header_matches = re.findall(r'-H\s+([^:]+):\s*([^ ]+)', _curl_command)
    for header in header_matches:
        # 生成请求头 : ( 这里是仅保留 terminal 和 token )
        if header[0].strip() in headers_useful:
            headers[header[0].strip()] = header[1].strip()

    # 返回数据 :
    return ReqData(url=url, method=method, headers=headers, data=data)


# curl 转 python 脚本 :
def curl_to_python(_curl_command):
    """
    将 curl 命令转换成 python 脚本
    并返回
    :param _curl_command:
    :return:
    """

    # curl 提取 : 浏览器控制台Curl的提取方式和Charles的Curl的提取方式不同 :
    if '--compressed' in _curl_command:
        curl_req_data = curl_extract_charles(_curl_command)
    else:
        curl_req_data = curl_extract(_curl_command)

    # 获取提取结果的 url, headers, method, data :
    url = curl_req_data.url
    headers = curl_req_data.headers
    data = curl_req_data.data
    method = curl_req_data.method

    # 因为 url 可能包含 "?" , 拼接参数, 这种情况下, 处理 url 中的参数:
    has_param = False
    if '?' in url:
        url_li = url.split('?', 1)
        url = url_li[0]
        # 处理 params : ( 过滤掉 _t 参数，并将其他参数转换为字典 )
        parsed_params = parse_qs(url_li[-1])
        data = {k: v[0] for k, v in parsed_params.items() if k != '_t'}
        has_param = True if data else False

    # 生成 python 代码 : ( 如果 Json 中含有 null/true/false, 则定义转换变量 )
    data_condition = 'None' in str(data) or 'True' in str(data) or 'False' in str(data)
    data_premise_code = """\nnull, true, false = None, True, False""" if data and data_condition else ''
    _python_code = f"""# coding=utf-8
# ____! Powered By Ginger !____
# _! Code By Curl Conversion !_

import requests
import os
import random
{data_premise_code}
# 防止本地开着代理导致运行异常 :
os.environ['no_proxy'] = 'yljk.cn'
res = None

# 请求接口和请求头 :
url = '{url}'
"""
    # 添加请求头 :
    if headers:
        _python_code += f"headers = {headers}\n\n"
    # 添加循环代码, 默认是循环 1 次,
    _python_code += f"# 默认添加一次循环, 如果需要构造数据, 只需要改一下 range(1), 再配合 _t 的代码示例进行数据构造 : \n"
    _python_code += "for i in range(1):\n"
    _python_code += "    # randmon 生成时间不一定用, 只是 randmon 写起来比较麻烦, 所以生成一个时间, 方便复制粘贴 ~ \n\n"
    _python_code += '    # 自动构造时间示例 : \n'
    _python_code += "    _y, _m = random.randint(2020, 2024), str(random.randint(1, 12)).zfill(2)\n"
    _python_code += "    _d = random.randint(1, 29) if _m == 2 else random.randint(1, 31) if _m in [1, 3, 5, 7, 8, 10, 12] else random.randint(1, 30)\n"
    _python_code += "    _d = str(_d).zfill(2)\n"
    _python_code += "    _H, _M, _S = str(random.randint(0, 24)).zfill(2), str(random.randint(0, 59)).zfill(2), str(random.randint(0, 59)).zfill(2)\n"
    _python_code += "    _t = f'{_y}-{_m}-{_d} {_H}:{_M}:{_S}'\n"
    _python_code += "    print(f'预生成的时间 : {_t}')\n\n"

    # 添加请求参数 :
    if data:
        # 输出格式化的 JSON 或 Python 字典
        formatted_data = json.dumps(data, indent=4, ensure_ascii=False, sort_keys=True)
        _python_code += f"    # 请求参数 : ( html 中 data 的缩进是有误的, 粘贴到 Pycharm 中格式化一下就好了 ~ 莫慌, 莫慌 ~ ) \n"
        _python_code += f'    data = '
        _python_code += f'''{formatted_data}\n'''
    _python_code += "\n"

    # 请求方法 :
    _python_code += f"    # 调用接口 : \n"
    _python_code += f'    try: \n'

    if method == 'get':
        _python_code += "        response = requests.get(url"
    elif method == 'post':
        _python_code += "        response = requests.post(url"
    elif method == 'put':
        _python_code += "        response = requests.put(url"
    elif method == 'delete':
        _python_code += "        response = requests.delete(url"
    else:
        _python_code += f"        response = requests.{method}(url"

    # 请求头 :
    if headers:
        _python_code += ", headers=headers"

    # 请求参数 :
    if data:
        if has_param:
            _python_code += ", params=data"
        else:
            _python_code += ", json=data"

    _python_code += ')\n'
    _python_code += '        res = response.text\n'
    _python_code += f'    except Exception as e: \n'
    _python_code += f'        res = e'
    _python_code += """

    # 打印结果 :
    # print(response.status_code)
    print(f'第 {i + 1} 次请求  --->  ', res)
"""

    return _python_code


# curl 转 python 脚本 ( 极简 ):
def curl_to_simple_python(_curl_command):
    # curl 提取 : 浏览器控制台Curl的提取方式和Charles的Curl的提取方式不同 :
    if '--compressed' in _curl_command:
        curl_req_data = curl_extract_charles(_curl_command)
    else:
        curl_req_data = curl_extract(_curl_command)

    # 获取提取结果的 url, headers, method, data :
    url = curl_req_data.url
    headers = curl_req_data.headers
    data = curl_req_data.data
    method = curl_req_data.method

    # 因为 url 可能包含 "?" , 拼接参数, 这种情况下, 处理 url 中的参数:
    has_param = False
    if '?' in url:
        url_li = url.split('?', 1)
        url = url_li[0]
        # 处理 params : ( 过滤掉 _t 参数，并将其他参数转换为字典 )
        parsed_params = parse_qs(url_li[-1])
        data = {k: v[0] for k, v in parsed_params.items() if k != '_t'}
        has_param = True if data else False

    # 生成 python 代码 : ( 如果 Json 中含有 null/true/false, 则定义转换变量 )
    data_condition = 'None' in str(data) or 'True' in str(data) or 'False' in str(data)
    data_premise_code = """\nnull, true, false = None, True, False""" if data and data_condition else ''
    _python_code = f"""# coding=utf-8
# ____! Powered By Ginger !____
# _! Code By Curl Conversion !_

import requests
import os
{data_premise_code}
os.environ['no_proxy'] = 'yljk.cn'

url = '{url}'
"""
    # 添加请求头 :
    if headers:
        _python_code += f"headers = {headers}\n"

    # 添加请求参数 :
    if data:
        # 输出格式化的 JSON 或 Python 字典
        formatted_data = json.dumps(data, indent=4, ensure_ascii=False, sort_keys=True)
        _python_code += f'data = '
        _python_code += f'''{formatted_data}\n'''

    _python_code += "\n"

    # 请求方法 :
    if method == 'get':
        _python_code += "response = requests.get(url"
    elif method == 'post':
        _python_code += "response = requests.post(url"
    elif method == 'put':
        _python_code += "response = requests.put(url"
    elif method == 'delete':
        _python_code += "response = requests.delete(url"
    else:
        _python_code += f"response = requests.{method}(url"

    # 请求头 :
    if headers:
        _python_code += ", headers=headers"

    # 请求参数 :
    if data:
        if has_param:
            _python_code += ", params=data"
        else:
            _python_code += ", json=data"

    _python_code += """)\n
print(response.json())
"""
    return _python_code


# curl 转 table 数据 :
def curl_to_table_data(_curl_command) -> ReqData:
    """
    将 curl 命令转换成 Excel 中的数据, 用于方便向 Excel 粘贴数据
    :param _curl_command:
    :return:
    """

    # curl 提取 : 浏览器控制台Curl的提取方式和Charles的Curl的提取方式不同 :
    if '--compressed' in _curl_command:
        curl_req_data = curl_extract_charles(_curl_command)
    else:
        curl_req_data = curl_extract(_curl_command)

    # 变量 :
    url = curl_req_data.url
    headers = curl_req_data.headers
    data = curl_req_data.data
    method = curl_req_data.method

    # 具体处理 :
    return ReqData(url=url, method=method, headers=headers, data=data)


# curl 转 ReqData :
def curl_to_req_data(_curl_command) -> ReqData:
    """
    对 Curl 进行提取
    :param _curl_command:
    :return:
    """
    # curl 提取 : 浏览器控制台Curl的提取方式和Charles的Curl的提取方式不同 :
    if '--compressed' in _curl_command:
        return curl_extract_charles(_curl_command)
    return curl_extract(_curl_command)


if __name__ == '__main__':
    # 命令行输入 curl 命令 :
    print("请输入 curl 命令 :")
    curl_command = """curl -H "user-agent: Dart/3.3 (dart:io)" -H "host: h-test.yljk.cn" -H "content-type: application/json" -H "terminal: APP_DOCTOR" -H "token: DOCTOR:61CPyNfceScyhADO0/AIIWO6vYM7IFQamU3rxH5g7cU=d7b2a7b28b7b4fea8b3d499f2de43193" --data-binary "{\"formDataId\":\"564174649453477888\",\"formType\":\"DAGL\",\"answerData\":{\"reportTime\":\"2024-08-06 19:48:00\",\"writeData\":[{\"content\":\"00:00\",\"itemType\":\"single\",\"regulation\":null,\"number\":1,\"itemId\":\"592360111616430081\",\"type\":\"TIME\"},{\"content\":\"00:00\",\"itemType\":\"single\",\"regulation\":null,\"number\":2,\"itemId\":\"592360111616430082\",\"type\":\"TIME\"},{\"content\":\"9\",\"itemType\":\"single\",\"regulation\":null,\"number\":3,\"itemId\":\"592360111616430083\",\"type\":\"SCORING\"},{\"content\":\"2024-08-06 19:36\",\"itemType\":\"single\",\"regulation\":null,\"number\":4,\"itemId\":\"592360111616430085\",\"type\":\"DATETIME\"},{\"content\":\"2024-08-06 19:36\",\"itemType\":\"single\",\"regulation\":null,\"number\":5,\"itemId\":\"592360111616430086\",\"type\":\"DATETIME\"},{\"content\":\"10\",\"itemType\":\"single\",\"regulation\":null,\"number\":6,\"itemId\":\"592360111616430087\",\"type\":\"SCORING\"}],\"saveOcrUseRecordParam\":{\"successCount\":0,\"failCount\":0,\"ocrImgUrl\":\"\",\"imgSourceType\":\"DOCTOR_ALBUM\"}},\"patientId\":\"577919968423837696\",\"fillDuration\":10}" -X PUT --compressed "https://h-test.yljk.cn/api/yft/form_data/update" """
    """
    curl_command = '''curl 'https://h-test.yljk.cn/api/yft/form_data/snapshot/submit' \
  -H 'Accept: application/json, text/javascript, */*; q=0.01' \
  -H 'Accept-Language: zh-CN,zh;q=0.9' \
  -H 'Connection: keep-alive' \
  -H 'Content-Type: application/json;charset=UTF-8' \
  -H 'Origin: https://h-test.yljk.cn' \
  -H 'Referer: https://h-test.yljk.cn/personManage/patientMange' \
  -H 'Sec-Fetch-Dest: empty' \
  -H 'Sec-Fetch-Mode: cors' \
  -H 'Sec-Fetch-Site: same-origin' \
  -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36' \
  -H 'sec-ch-ua: "Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "Windows"' \
  -H 'terminal: WEBPC' \
  -H 'token: ENT_USER:gIkEwpZMwv4yLuMuRZVaSSXDxbYoyJNjUAhf1ammZqE=b5aadf8c10014661bef4bca92edb4463' \
  --data-raw '{"snapshotId":"595198087895584775","patientId":"542641665960890368","formType":"ZDYDA","answerData":{"writeData":[{"itemType":"element","itemId":"595198087895584769","code":"heightWeight","writeData":[[{"itemType":"single","itemId":"587381329201598464","content":"180"},{"itemType":"single","itemId":"587381329201598465","content":"80"},{"itemType":"single","itemId":"587381329201598466"}]]}],"reportTime":"2024-08-14 10:55:31"}}'
    '''
    """
    # 键盘输入 , 直到输入为空时退出 :
    """
    while True:
        try:
            # 直接 input 会导致获取不到请求头和参数 !!!
            line = input()
            if line:
                curl_command += line + "\n"
            else:
                break
        except EOFError:
            break
    # """

    # 将 curl 命令转换成 python 脚本 :
    """
    python_code = curl_to_python(curl_command)
    print("\n生成的 Python 脚本为 :\n")
    print(python_code)
    # """

    # curl 提取 :
    # """
    # print(curl_extract(curl_command))
    # """

    print(curl_extract(curl_command))
    print(curl_extract_charles(curl_command))

    print(curl_to_python(curl_command))
